Skip to content

feat(eew): announce predicted intensity before warning sound - #546

Merged
lowrt merged 4 commits into
ExpTechTW:mainfrom
archie0732:codex/eew-spoken-intensity
Sep 6, 2026
Merged

feat(eew): announce predicted intensity before warning sound#546
lowrt merged 4 commits into
ExpTechTW:mainfrom
archie0732:codex/eew-spoken-intensity

Conversation

@archie0732

@archie0732 archie0732 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

這個 PR 做了什麼

在強震監視器顯示於前景時,以系統 TTS 朗讀最新 EEW 預估震度,朗讀完成後才播放原有警示音;新報會中斷舊播報並改讀最新結果。可在「更多 → 通知」關閉。

相關 issue

四則 commit

commit
feat(eew): announce predicted intensity before warning sound 功能本體,含 flutter_tts 的 git 相依
build(android): include x86_64 in debug builds 模擬器需要
ci(android): build the pull-request APK without the release keystore 修好本 PR 的 Android CI
feat(eew): let the user turn off the spoken intensity announcement 開關

怎麼驗

手動測試(Android Emulator,x86_64)

tool\run.ps1 -d emulator-5554 --dart-define=DPIP_DEMO_MONITOR=true --dart-define=DPIP_DEMO_MONITOR_SEVERE=true --dart-define=DPIP_DEMO_MONITOR_SOUND=true

進入「雷達」,切換為「強震監視器」:

  • 聽得到朗讀預估震度
  • 朗讀完成後才播放警示音,兩者不重疊
  • 離開強震監視器後立即停止播報
  • 快速切換分頁十次不閃退(驗 MethodChannel.Result 只回一次)

自動測試

  • tool/check/commits.sh upstream/main..HEAD:4 commits 通過
  • tool/dev/analyze.shflutter analyze 無問題
  • tool/check/l10n.shlayering.shstorage.shnotification_sounds.sh
  • 新增測試:前景播報與警示音排序、逾時 fallback、最新報取代舊報、忽略重複與較舊報次、資料過期停止播報、中文五弱/五強/六弱/六強朗讀文字與震度範圍保護、開關的預設值與持久化、設定列的 UI

test/tool/* 在 Windows 上有 55 個失敗(Git Bash 把路徑轉成 D:GitHub...),另有一個 semantics_overlay_portal_test 失敗——該檔案本次未修改且在 CI 上為綠。

安全關鍵的部分

播報只在前景、可見的監視器上發生;背景與終止狀態的推播完全不經過這個閘門。TTS 引擎不存在或卡住時有兩層逾時(控制器 8 秒、通知閘門 10 秒),任何情況下都不會讓警示通知被無限期壓住——最壞情況是警報比平常響,不會晚。feed 進入 stale/offline 時直接停止播報。關閉開關走的是同一條路徑:停掉語音並釋放閘門扣住的通知。

尚未驗證

iOS 完全沒有實機或模擬器測試,開發環境為 Windows。

flutter_tts 取自 ExpTechTW 的 fork(見下),該 fork 的 Swift Package Manager 支援也只做過靜態檢查,沒有被任何編譯器看過。本 PR 的 iOS CI job 是它第一次實際建置。

相依:為什麼用 fork

flutter_tts 不支援 Swift Package Manager,而本專案的 iOS 端不使用 CocoaPods(README → 參與開發)。Flutter 每次建置都警告並說明「will become an error in a future version」,在那之前它會透過相依關係把 Podfile 帶回專案。

上游 dlutton/flutter_tts 從 2026-05 起有三個功能相同的 SPM PR(#631、#634、#651)都未合併,因此改用 ExpTechTW/flutter_tts,內容是移植 #651(作者 @shivanshu877,cherry-pick 保留署名)。釘在 fork master 的 a9cd6eda,比照 maplibre_gl 的做法。

檢查清單

  • tool/check/commits.sh origin/main..HEAD 通過
  • 一個 commit 一件事
  • flutter analyze 通過;新增測試全數通過
  • 新的使用者可見字串都走 AppLocalizations,沒有寫死(含新增的粵語 app_yue.arb
  • 有 UI 變更:「更多 → 通知」新增一列,沿用既有的 _MoreTile,未引入新的樣式

@lowrt

lowrt commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

記得要 rebase before merging

@archie0732
archie0732 force-pushed the codex/eew-spoken-intensity branch from ad76de1 to 1f489c4 Compare September 5, 2026 07:30
@lowrt

lowrt commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

@archie0732

The following plugins do not support Swift Package Manager for ios:

  • flutter_tts
    This will become an error in a future version of Flutter. Please contact the plugin maintainers to request Swift Package Manager adoption.

@lowrt

lowrt commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

@archie0732
https://github.com/dlutton/flutter_tts/pulls
看起來裡面有不少人抄作業Network graph (而且有三個PR😂

另外剛創 https://github.com/ExpTechTW/flutter_tts 可以開PR

@archie0732

Copy link
Copy Markdown
Contributor Author

@archie0732 https://github.com/dlutton/flutter_tts/pulls 看起來裡面有不少人抄作業Network graph (而且有三個PR😂

另外剛創 https://github.com/ExpTechTW/flutter_tts 可以開PR

maplibre_gl 那條路我沒想到,fork 確實是這個 repo 既有的做法。上游那三個 PR 從 5 月放到現在都沒合,等作者應該是等不到了。

不過在你留言之前我已經先改成 app 自有的 channel 了(c537a540),所以想先把兩個方案的現況擺出來,你決定要哪一個我照做。

現在這版(自有 channel)

com.exptech.dpip/speech,SpeechPlugin.swift + SpeechChannel.kt,兩邊各約 30 行,跟現有的 CompassPlugin / ScreenWakeChannel 同一個模子
SpeechService 原本就是介面、flutter_tts 只在一個實作裡,所以上層的控制器、閘門和那 10 個測試一行未改
pubspec.yaml 和 pubspec.lock 現在跟 main 完全相同,這個 PR 不再引入任何相依
Android 實機驗過四項:聽得到朗讀、講完才響、離開就停、快速切換分頁十次不閃退(這項是在驗 MethodChannel.Result 只回一次)
代價:只實作 speak / stop,沒有語音選擇、語速、音高、佇列
fork + SPM

保留完整功能,之後要調語速那類的不用再寫原生
代價是 ExpTechTW 要長期維護那個 fork、追上游
流程上多一輪:移植 SPM 改動 → 在 fork 開 PR → 合併 → 回來改 DPIP 的相依
以這個功能目前只需要「講一句話、講完通知我」來說,我覺得自有 channel 的成本比較低;但如果之後預期會用到 flutter_tts 的其他能力,那 fork 比較划算,這個你比我清楚。

另外不論走哪條路,我沒有 Mac,iOS 端的執行期行為(語音真的出得來、audio session 不會蓋掉別的 app)都要麻煩有裝置的人幫忙看一下。

你說走 fork 我就把 c537a54 拿掉改成 git 相依,覺得自有 channel 可以就維持現狀。

@archie0732
archie0732 marked this pull request as ready for review September 5, 2026 13:15
@archie0732
archie0732 requested a review from a team as a code owner September 5, 2026 13:15
@lowrt

lowrt commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

@whes1015 起床再看方案說法

經過測試iOS模擬器有說話 預估所在地震度
然後這個沒有設定可以開關?

@BigDuck430

BigDuck430 commented Sep 5, 2026

Copy link
Copy Markdown
Member

五弱 五強 六弱 六強的部份,有需要加「等級」嗎?因為地牛是直接五弱 這樣感覺贅字也比較少
給個小建議 謝謝

@whes1015

whes1015 commented Sep 6, 2026

Copy link
Copy Markdown
Member

@archie0732 https://github.com/dlutton/flutter_tts/pulls 看起來裡面有不少人抄作業Network graph (而且有三個PR😂
另外剛創 https://github.com/ExpTechTW/flutter_tts 可以開PR

maplibre_gl 那條路我沒想到,fork 確實是這個 repo 既有的做法。上游那三個 PR 從 5 月放到現在都沒合,等作者應該是等不到了。

不過在你留言之前我已經先改成 app 自有的 channel 了(c537a540),所以想先把兩個方案的現況擺出來,你決定要哪一個我照做。

現在這版(自有 channel)

com.exptech.dpip/speech,SpeechPlugin.swift + SpeechChannel.kt,兩邊各約 30 行,跟現有的 CompassPlugin / ScreenWakeChannel 同一個模子 SpeechService 原本就是介面、flutter_tts 只在一個實作裡,所以上層的控制器、閘門和那 10 個測試一行未改 pubspec.yaml 和 pubspec.lock 現在跟 main 完全相同,這個 PR 不再引入任何相依 Android 實機驗過四項:聽得到朗讀、講完才響、離開就停、快速切換分頁十次不閃退(這項是在驗 MethodChannel.Result 只回一次) 代價:只實作 speak / stop,沒有語音選擇、語速、音高、佇列 fork + SPM

保留完整功能,之後要調語速那類的不用再寫原生 代價是 ExpTechTW 要長期維護那個 fork、追上游 流程上多一輪:移植 SPM 改動 → 在 fork 開 PR → 合併 → 回來改 DPIP 的相依 以這個功能目前只需要「講一句話、講完通知我」來說,我覺得自有 channel 的成本比較低;但如果之後預期會用到 flutter_tts 的其他能力,那 fork 比較划算,這個你比我清楚。

另外不論走哪條路,我沒有 Mac,iOS 端的執行期行為(語音真的出得來、audio session 不會蓋掉別的 app)都要麻煩有裝置的人幫忙看一下。

你說走 fork 我就把 c537a54 拿掉改成 git 相依,覺得自有 channel 可以就維持現狀。

我覺得用 fork 比較合適,追上游問題不大,如果 iOS / Android 沒有對 flutter_tts 的 breaking change ,不追也沒關係。

@archie0732

Copy link
Copy Markdown
Contributor Author

五弱 五強 六弱 六強的部份,有需要加「等級」嗎?因為地牛是直接五弱 這樣感覺贅字也比較少 給個小建議 謝謝

這個我在程式碼那裡有說明:
Chinese, Japanese, and Korean get their conventional weak/strong words; the Chinese split levels keep a trailing 等級 because Google zh-TW can swallow a sentence-final 強 even though it reports the utterance as completed.
— spoken_intensity.dart

主要是我用模擬器聽的時候句子會斷,像是「所在地預估震度,五弱。」。Google 的 zh-TW 語音會把句尾那個 弱 / 強 吃掉,而且它還是回報「已念完」。實際上聽到的是「所在地預估震度,五」—— 五級跟五弱差很多。不過主要還是看你們的偏好,這裡都可以在調。

@archie0732

Copy link
Copy Markdown
Contributor Author

@archie0732 https://github.com/dlutton/flutter_tts/pulls 看起來裡面有不少人抄作業Network graph (而且有三個PR😂
另外剛創 https://github.com/ExpTechTW/flutter_tts 可以開PR

maplibre_gl 那條路我沒想到,fork 確實是這個 repo 既有的做法。上游那三個 PR 從 5 月放到現在都沒合,等作者應該是等不到了。
不過在你留言之前我已經先改成 app 自有的 channel 了(c537a540),所以想先把兩個方案的現況擺出來,你決定要哪一個我照做。
現在這版(自有 channel)
com.exptech.dpip/speech,SpeechPlugin.swift + SpeechChannel.kt,兩邊各約 30 行,跟現有的 CompassPlugin / ScreenWakeChannel 同一個模子 SpeechService 原本就是介面、flutter_tts 只在一個實作裡,所以上層的控制器、閘門和那 10 個測試一行未改 pubspec.yaml 和 pubspec.lock 現在跟 main 完全相同,這個 PR 不再引入任何相依 Android 實機驗過四項:聽得到朗讀、講完才響、離開就停、快速切換分頁十次不閃退(這項是在驗 MethodChannel.Result 只回一次) 代價:只實作 speak / stop,沒有語音選擇、語速、音高、佇列 fork + SPM
保留完整功能,之後要調語速那類的不用再寫原生 代價是 ExpTechTW 要長期維護那個 fork、追上游 流程上多一輪:移植 SPM 改動 → 在 fork 開 PR → 合併 → 回來改 DPIP 的相依 以這個功能目前只需要「講一句話、講完通知我」來說,我覺得自有 channel 的成本比較低;但如果之後預期會用到 flutter_tts 的其他能力,那 fork 比較划算,這個你比我清楚。
另外不論走哪條路,我沒有 Mac,iOS 端的執行期行為(語音真的出得來、audio session 不會蓋掉別的 app)都要麻煩有裝置的人幫忙看一下。
你說走 fork 我就把 c537a54 拿掉改成 git 相依,覺得自有 channel 可以就維持現狀。

我覺得用 fork 比較合適,追上游問題不大,如果 iOS / Android 沒有對 flutter_tts 的 breaking change ,不追也沒關係。

ok,#546
那麼要等 fork 的 SPM PR 合併,還是先用 pub.dev 的 flutter_tts 合進來,SPM 那件事另外開一則 PR呢?

@archie0732

Copy link
Copy Markdown
Contributor Author

flutter_tts 的 SPM 支援已經開好 PR:ExpTechTW/flutter_tts#1

移植上游 dlutton/flutter_tts#651(作者 @shivanshu877),用 cherry-pick 保留原作者署名,13 檔案 +62 −26,與該 PR 一字不差。base 就是 fork 的 master,套用時無衝突。

沒有 macOS 環境,所以完全沒有實際建置過。 只做了靜態檢查:

  • pubspec.yamlpluginClass 對得上兩個平台的 public class FlutterTtsPlugin
  • Package.swift 的 package 名稱/product/target 命名與 url_launcher_ios 等官方 plugin 的慣例一致
  • Sources/flutter_tts/ 底下確實有原始碼,podspec 的 glob 也指向該處
  • plugin 的最低版本(iOS 12.0)低於 DPIP 的 15.0,不會擋掉建置

合併前需要有 Mac 的人跑一次 flutter build ios 確認。那則 PR 裡另外有一個版本號的問題想請你決定。

這個 PR 接下來

#1 合併之後:

  1. 拿掉 refactor(eew): speak through an app-owned channel,刪除 SpeechPlugin.swiftSpeechChannel.kt
  2. pubspec.yaml 改成 git 相依指向 ExpTechTW/flutter_tts

想先確認一件事:#546 要等 #1 合併,還是先用 pub.dev 的 flutter_tts 合進來、之後再換成 fork? SPM 目前只是警告,是否先合再換,我認為可以讓這個 PR 不用一直掛著等另一個 repo。

另外

@BigDuck430 提的語音開關已經做好了在 「更多 → 通知」那裡多一個開關,預設開,整列都可以點。關掉時會停掉正在播報的語音,關開關應該永遠不會延後警報,最壞情況是警報比平常早響。

目前實作在 SpeechService 介面之上,底層用哪個 TTS 實作都不影響,會跟著下次推送一起上來。(可能會晚點)

@archie0732
archie0732 force-pushed the codex/eew-spoken-intensity branch from c537a54 to 2201218 Compare September 6, 2026 12:06
New(zh-Hant): 強震監視器會先朗讀地震預估震度,再播放警示音
New(en-US): the seismic monitor announces predicted intensity before the warning sound
A pull request opened from a fork is handed no repository secrets, so
`secrets.KEYSTORE_BASE64` and its three companions arrive as the empty
string. The job still decoded them: it wrote a zero-byte
android/app/my-release-key.jks and an android/key.properties whose four
values were blank, and Gradle then died inside packageRelease on a
keystore it could not read. Nothing in that failure names a fork, and
every other check on the same commit passes — the iOS build included,
because it never codesigns.

This job was never the one that signs. The header of this file already
says so: release.yml builds the signed, uploadable artifact on a push to
main, and what comes out of here is the copy nobody installs. So the
keystore is dropped rather than guarded. android/app/build.gradle.kts
already falls back to the debug signing config when android/key.properties
is absent, which makes the pull-request APK the same release build,
debug-signed.
New(zh-Hant): 「更多 → 通知」可以關閉強震監視器朗讀預估震度的語音
New(en-US): the monitor's spoken intensity announcement can be switched off under More → Notifications
New(ja-JP): 強震モニタの予想震度の読み上げを「その他 → 通知」でオフにできます
@archie0732
archie0732 force-pushed the codex/eew-spoken-intensity branch from 2201218 to 12b5720 Compare September 6, 2026 12:42
@lowrt
lowrt merged commit 1c1ce8b into ExpTechTW:main Sep 6, 2026
6 of 7 checks passed
@lowrt

lowrt commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

@archie0732
1.那個通知類別朗讀不應該放那裡,應該要放在無障礙功能
2.預設要關閉
3.沒有實作重播裡面

@archie0732

Copy link
Copy Markdown
Contributor Author

@archie0732 1.那個通知類別朗讀不應該放那裡,應該要放在無障礙功能 2.預設要關閉 3.沒有實作重播裡面

ok了解,我晚點來用

@archie0732

Copy link
Copy Markdown
Contributor Author

@lowrt

關於你說的第2點和第3點我目前都改好了。

關於第1點想跟你確認一下位置,在更多那裡沒有看到無障礙這個區塊:

更多現在的分區是 地區 / 通知 / 顯示 / Mesh 網路 / 進階 / 相關連結 / 取得 App / 測試版 / 合作夥伴 / 關於。

我剛剛找了一下:無障礙相關的設定: 色覺調整、對比、文字大小、字重是放在顯示設定裡。

目前我是有想到兩種解法:

  1. 一樣放在顯示設定裡面(不過語音在顯示設定有點怪? 🤔)
  2. 單獨創一個無障礙的區域然後放在裡面(不過就只有這個設定,不過後續可以在新增就是了)

看你們的偏好,我都可以配合,不過我的多益還沒讀完可能要約兩個小時才會在做修改就是了😅

@lowrt

lowrt commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

@lowrt

關於你說的第2點和第3點我目前都改好了。

關於第1點想跟你確認一下位置,在更多那裡沒有看到無障礙這個區塊:

更多現在的分區是 地區 / 通知 / 顯示 / Mesh 網路 / 進階 / 相關連結 / 取得 App / 測試版 / 合作夥伴 / 關於。

我剛剛找了一下:無障礙相關的設定: 色覺調整、對比、文字大小、字重是放在顯示設定裡。

目前我是有想到兩種解法:

1. 一樣放在顯示設定裡面(不過語音在顯示設定有點怪? 🤔)

2. 單獨創一個無障礙的區域然後放在裡面(不過就只有這個設定,不過後續可以在新增就是了)

看你們的偏好,我都可以配合,不過我的多益還沒讀完可能要約兩個小時才會在做修改就是了😅

可以創新的區域

@archie0732 archie0732 mentioned this pull request Sep 7, 2026
9 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

唸出地震預測震度

4 participants